package vpc.ext.vstack;

import vpc.core.Program;
import vpc.core.Value;
import vpc.core.ProgramDecl;
import vpc.hil.*;
import vpc.hil.parser.Token;
import vpc.ext.vstack.util.*;

import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;

public class InterruptMask {

	/*************** DATA *****************/
	private static DeviceDecl device = null;

	private static Map<Register, Set<Interrupt>> mapReg = null;

	private static boolean initialized = false;

	private static int numInterrupts = 0;

	private final Map<Interrupt, Boolean> mask;

	private boolean masterBit;

	/*************** CONSTRUCTORS ***************/

	/**
	 * Initial constructor for the InterruptMask. The InterruptMask of the
	 * root node of the graph uses this constructor to create a mask that
	 * specifies all interrupts to be disabled.
	 */
	public InterruptMask(DeviceDecl device) {
		assert (InterruptMask.initialized == false);

		//No device, so don't initialize the InterruptMask
		if (device == null) {
			this.mask = null;
		} else {

			InterruptMask.init(device);
			this.mask = new HashMap<Interrupt, Boolean>(numInterrupts + 1,
					(float) 1);

			for (Interrupt i : device.interrupts) {
				mask.put(i, false);
			}
			// ASSUMPTION: initially, the master bit is assumed to be set
			this.masterBit = true;
		}
	}

	/**
	 * Secondary InterruptMask constructor. This must be used AFTER the primary
	 * constructor has created the initial empty mask for the root node. Creates
	 * a new, empty mask.
	 */
	public InterruptMask() {
		assert (InterruptMask.initialized == true);

		if (InterruptMask.device == null) {
			this.mask = null;
		} else {
			this.mask = new HashMap<Interrupt, Boolean>(numInterrupts + 1,
					(float) 1);

			for (Interrupt i : device.interrupts) {
				mask.put(i, false);
			}
			this.masterBit = true;
		}
	}

	/**
	 * Secoundary InterruptMask constructor. This must be used AFTER the primary
	 * constructor has created the initial empty mask for the root node. Creates
	 * a clone of the previous InterruptMask.
	 * @param previousMask The mask to be copied.
	 */
	public InterruptMask(InterruptMask previousMask) {
		assert (InterruptMask.initialized == true);
		if (InterruptMask.device != null) {
			this.mask = previousMask.copyMask();
			this.masterBit = previousMask.masterBit;
		} else {
			this.mask = null;
		}
	}

	/*************** STATIC CLASS METHODS **************/

	public static boolean hasDevice() {
		return (InterruptMask.device == null);
	}

	private static boolean matchReg(Interrupt i, Register r) {
		if (i == null || r == null) {
			return false;
		}
		String regName = r.getName();

		String flagRegString = null;
		if (i.flagReg != null) {
			flagRegString = i.flagReg.toString();
		}

		String maskRegString = null;
		if (i.maskReg != null) {
			maskRegString = i.maskReg.toString();
		}

		if (regName == null || flagRegString == null || maskRegString == null) {
			return false;
		}

		return flagRegString.equals(regName) || maskRegString.equals(regName);
	}

	/**
	 * Static initialization method for the InterruptMask class. Sets up all of the register/interrupt
	 * pairs. Is invoked by the primary constructor for the class.
	 * @param device
	 */

	private static void init(DeviceDecl device) {
		Debug.Flag localFlag = Debug.Flag.INTR_MASK;

		if (InterruptMask.initialized == true) {
			return;
		}

		assert (device != null);

		InterruptMask.device = device;

		if (InterruptMask.mapReg == null) {
			InterruptMask.mapReg = new HashMap<Register, Set<Interrupt>>();
		}

		if (device == null) {
			System.out.println("Device is null");
			return;
		}
		for (Register r : device.registers) {
			Set<Interrupt> matchingInterrupts = new HashSet<Interrupt>();

			for (Interrupt i : device.interrupts) {
				if (matchReg(i, r)) {
					matchingInterrupts.add(i);
				}
			}

			if (!matchingInterrupts.isEmpty()) {
				InterruptMask.mapReg.put(r, matchingInterrupts);
			} else {
				matchingInterrupts = null;
			}
		}

		InterruptMask.initialized = true;

		Debug.println(mapToString(), localFlag);

		return;
	}

	/**
	 * Getter method for the given Register key.
	 * @param r The register key.
	 * @return The Set of Interrupts bound to the given register key, null if no such key exists.
	 */
	public static Set<Interrupt> getInterrupts(Register r) {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);
		return mapReg.get(r);
	}

	/**
	 * Debug method for the internal state of the InterruptMask class.
	 * TODO: comment this out
	 * @return A String representation of the interruptMask class
	 */

	public static String mapToString() {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		StringBuffer buff = new StringBuffer();
		for (Register r : mapReg.keySet()) {
			buff.append("(reg = " + r.getName() + ", intr = ");
			for (Interrupt i : mapReg.get(r)) {
				buff.append(i.getName() + "/"
						+ DataHandler.bitToBase10(i.maskBit) + ", ");
			}
			buff.append(")\n");
		}
		return buff.toString();
	}

	/**
	 * This method calculates the set of interrupts that are enabled
	 * by setting the register with the given constant, raw value.
	 * @param register The register that is being set
	 * @param value A Virgil Value that represents a raw, constant value
	 * @return The non-null, but potentially empty, list of enabled Interrupts.
	 */
	public static Set<Interrupt> getEnabled(Register register, Value value) {

		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		Set<Interrupt> regInterrupts = null;
		Set<Interrupt> enabledInterrupts = new HashSet<Interrupt>();

		regInterrupts = InterruptMask.getInterrupts(register);
		if (regInterrupts != null) {

			//Get the numerical representation of the constant, raw value
			long bitMask = vpc.core.base.PrimRaw.fromValue(value);

			for (Interrupt i : regInterrupts) {
				//TODO : determine if we should be comparing against the
				//flag bit or the mask bit of the interrupt token
				long iMask = DataHandler.bitToBase10(i.maskBit);

				if ((bitMask & iMask) != 0) {
					// The resulting mask is non-zero, so the interrupt is enabled
					enabledInterrupts.add(i);
				}
			}
		}

		return enabledInterrupts;
	}

	/**
	 * This function determines whether the given EntryPoint represents the interrupt handler
	 * for the Interrupt argument, based on their names. This function ASSUMES that each interrupt
	 * handler's name is identical to the interrupt's name.
	 * @param i An interrupt
	 * @param e A potential interrupt handler for the interrupt
	 * @return True if a match is found, false otherwise.
	 */
	private static boolean match(Interrupt i, vpc.core.ProgramDecl.EntryPoint e) {
		return (i.getName().equals(e.getName()));
	}

	/**
	 * Given an interrupt i and a program p, determine the singular Virgil Method
	 * that is designated as the interrupt's handler.
	 * ASSUMPTIONS:
	 * - Each interrupt has exactly one handler
	 * - The name of the entrypoint is the name of the interrupt
	 * @param i The interrupt who's handler we would like to find
	 * @param p The program we are examining
	 * @return A Method representing the interrupt's handler (if found), NULL otherwise.
	 */
	public static vpc.core.decl.Method getInterruptHandler(Interrupt i,
			Program p) {
		vpc.core.decl.Method handler = null;
		for (vpc.core.ProgramDecl.EntryPoint e : p.programDecl.entryPoints) {
			if (match(i, e)) {
				handler = e.method;
				break;
			}
		}
		return handler;
	}

	public static boolean isInterruptHandler(vpc.core.decl.Method m, Program p) {
		boolean result = false;
		for (vpc.core.ProgramDecl.EntryPoint ep : p.programDecl.entryPoints) {
			if (m.getName().equals(ep.getName())) {
				result = true;
				break;
			}
		}
		return result;
	}

	/*************** OBJECT METHODS **************/

	public Set<Interrupt> getInterrupts() {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);
		assert (this.mask != null);

		return this.mask.keySet();
	}

	/**
	 * Enabled all interrupts in the mask.
	 */
	public void enabledAll() {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		for (Interrupt i : this.mask.keySet()) {
			this.enableInterrupt(i);
		}
	}

	/**
	 * Determine whether the given Interrupt is enabled.
	 * @param i An Interrupt key.
	 * @return True if the key is found AND it is enabled, false otherwise.
	 */
	public boolean isEnabled(Interrupt i) {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		boolean result = false;
		if (this.mask.containsKey(i)) {
			result = this.mask.get(i);
		}
		return result;
	}

	/**
	 * Enables the given interrupt IF the interrupt is registered with the mask.
	 * @param i The interrupt to enable.
	 */
	public void enableInterrupt(Interrupt i) {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		if (this.mask.containsKey(i)) {
			this.mask.put(i, true);
		}
		return;
	}

	/**
	 * Disables the interrupt IF the interrupt is registered with the mask.
	 * @param i The interrupt to disable.
	 */
	public void disableInterrupt(Interrupt i) {
		assert (InterruptMask.initialized == true);
		assert (InterruptMask.device != null);

		if (this.mask.containsKey(i)) {
			this.mask.put(i, false);
		}
		return;
	}

	/**
	 * Enables the abstract hardware master bit (enables interrupts).
	 *
	 */
	public void enableMaster() {
		this.masterBit = true;
	}

	/**
	 * Disables the abstract hardware master bit (disables interrupts).
	 *
	 */
	public void disableMaster() {
		this.masterBit = false;
	}

	public boolean masterBitIsEnabled() {
		return this.masterBit;
	}

	/**
	 * Returns a string representation of the InterruptMask as Interrupt/Boolean pairs.
	 */
	public String toString() {
		if ((InterruptMask.initialized == false)
				|| (InterruptMask.device == null)) {
			return "()";
		}

		StringBuffer buff = new StringBuffer();
		for (Interrupt i : this.mask.keySet()) {
			boolean enabled = this.mask.get(i);
			if (enabled == true) {
				buff.append("(" + i.getName() + ", enabled)");
			}
		}
		return buff.toString();
	}

	/**
	 * Internal method to copy this object's Interrupt/Boolean mapping.
	 * @return
	 */
	private Map<Interrupt, Boolean> copyMask() {
		Map<Interrupt, Boolean> oldMask = this.mask;
		int length = oldMask.size();
		float loadFactor = 1;
		Map<Interrupt, Boolean> newMask = new HashMap<Interrupt, Boolean>(
				length, loadFactor);
		for (Interrupt i : oldMask.keySet()) {
			newMask.put(i, oldMask.get(i));
		}
		return newMask;
	}

}
